<template>
  <transition :name="animation">
    <div v-if="currentVisible" v-show="!currentMinimized" class="my-dialog__wrapper" :style="wrapperStyle">
      <div class="my-dialog__modal" v-if="modal"></div>
      <MyDrag ref="dialog"
              @mousedown.native="handleMousedown"
              v-clickoutside="handleClickOutside"
              v-bind="dragOptions"
              :class="dialogClass"
              :style="dialogStyle"
              @stop="handleDragStop"
              @drag="handleDrag"
              @start="handleDragStart">
        <MyResize ref="resize"
                  v-bind="resizeOptions"
                  @start="handleResizeStart"
                  @stop="handleResizeStop"
                  @resize="handleResize">
          <Panel ref="panel"
                 v-bind="$attrs"
                 :title="title"
                 :icon="iconOptions"
                 :width="dialogWidth"
                 :height="dialogHeight"
                 :submit-text="submitText"
                 :cancel-text="cancelText"
                 :submit-loading="submitLoading"
                 :footer="footer"
                 :theme="theme"
                 :closable="closable"
                 :before-close="beforeClose"
                 :maximizable="maximizable"
                 :maximized.sync="currentMaximized"
                 :minimizable="minimizable"
                 :minimized.sync="currentMinimized"
                 @submit="handleSubmit"
                 @cancel="handleCancel"
                 @close="handleClose">
            <template v-if="$slots.icon" v-slot:icon>
              <slot name="icon"></slot>
            </template>
            <template v-if="$slots.title" v-slot:title>
              <slot name="title"></slot>
            </template>
            <template v-if="$slots.tool" v-slot:tool>
              <slot name="tool"></slot>
            </template>
            <template v-if="$slots.footer" v-slot:footer>
              <slot name="footer"></slot>
            </template>
            <MySpin fit :tip="loadingTip" :loading="loading" ref="mySpin">
              <SrcFrame v-if="src" :src="src" @load="handleSrcLoad"></SrcFrame>
              <slot v-else></slot>
            </MySpin>
          </Panel>
        </MyResize>
      </MyDrag>
    </div>
  </transition>
</template>

<script>
import MyDrag from '../drag'
import MyResize from '../resize'
import Panel from '../panel'
import MySpin from '../spin'

import {addResizeListener, removeResizeListener} from 'element-ui/lib/utils/resize-event'
import clickoutside from 'element-ui/lib/utils/clickoutside'
import {addClass, removeClass} from 'element-ui/lib/utils/dom'

let Z_INDEX = 100

/**
 * 适配百分比或像素单位
 * @private
 * @param {number} max 最大的长度
 * @param {string} value 要转换的长度值
 * @return {number}
 */
function getLength(max, value) {
  if (!value) return 0
  const percent = value.includes('%')
  const val = Number.parseFloat(value)
  return percent ? max * val / 100 : val
}

export default {
  name: 'sys-dialog',
  directives: {
    clickoutside
  },
  components: {
    MyDrag,
    MyResize,
    Panel,
    MySpin
  },
  /**
   * 属性参数
   * @member props
   * @property {boolean} [visible=true] 显示弹窗，支持sync修饰符
   * @property {string} [title] 标题文本，复杂内容可通过插槽定义
   * @property {string|object} [icon] 标题前的图标，可以是字体图标或svg
   * @property {string} [width] 弹窗宽度
   * @property {string} [height] 高度
   * @property {boolean} [modal] 显示遮罩层
   * @property {string} [theme] 主题风格，可选值：'primary', 'dark', 'light'
   * @property {boolean|object} [draggable] 拖拽配置，参考MyDrag组件
   * @property {boolean|object} [resizable] resize配置，参考MyResize组件
   * @property {string} [animation=el-fade-in] 显示动画
   * @property {boolean} [footer=true] 显示底部
   * @property {string} [submitText=确定] 确定按钮文本
   * @property {string} [cancelText=取消] 取消按钮文本
   * @property {boolean} [submitLoading] 确定按钮显示loading，防止重复提交
   * @property {boolean} [closable=true] 窗体可关闭
   * @property {function} [beforeClose] 窗体关闭前进行的操作，必须要返回Promise
   * @property {boolean} [maximizable] 可最大化
   * @property {boolean} [maximized] 初始是否最大化
   * @property {boolean} [minimizable] 可最小化，最小化只隐藏，不销毁组件
   * @property {boolean} [minimized] 初始是否最小化
   * @property {boolean} [cancelClose] 点击取消按钮关闭窗体
   * @property {boolean} [loading] 显示loading
   * @property {boolean} [loadingTip=正在拼命加载...] loading提示文本
   * @property {string} [src] 用iframe加载的页面地址
   * @property {string} [bodyClass] dialog内容容器className
   * @property {string} [target] 窗体加载到容器的html选择器
   * @property {boolean} [closeOnClickOutside] 点击窗体外部关闭
   */
  props: {
    // 显示
    visible: {
      type: Boolean,
      default: true
    },
    title: String,
    icon: [String, Object],
    width: String,
    height: String,
    modal: Boolean,
    theme: {
      type: String,
      default: 'light',
      validator(val) {
        return ['primary', 'dark', 'light'].includes(val)
      }
    },
    draggable: [Boolean, Object],
    resizable: [Boolean, Object],
    animation: {
      type: String,
      default: 'el-fade-in'
    },
    footer: {
      type: Boolean,
      default: true
    },
    submitText: {
      type: String,
      default: '确定'
    },
    submitLoading: Boolean,
    cancelText: {
      type: String,
      default: '取消'
    },
    closable: {
      type: Boolean,
      default: true
    },
    beforeClose: Function,
    maximizable: Boolean,
    maximized: Boolean,
    minimizable: Boolean,
    minimized: Boolean,
    cancelClose: {
      type: Boolean,
      default: true
    },
    loading: Boolean,
    loadingTip: {
      type: String,
      default: '正在拼命加载...'
    },
    src: String,
    bodyClass: String,
    // 窗体加载到容器的html选择器
    target: String,
    closeOnClickOutside: Boolean
  },
  data() {
    return {
      currentVisible: this.visible,
      viewWidth: 0,
      viewHeight: 0,
      viewLeft: 0,
      viewTop: 0,
      originalWidth: 0,
      originalHeight: 0,
      dialogWidth: 0,
      dialogHeight: 0,
      zIndex: ++Z_INDEX, // Z_INDEX,
      currentMaximized: this.maximized,
      currentMinimized: false
    }
  },
  watch: {
    width() {
      this.$nextTick(() => {
        this.updateView()
      })
    },
    height() {
      this.$nextTick(() => {
        this.updateView()
      })
    },

    visible(val) {
      this.currentVisible = val
      // 如果不可见，最小化已经无意义，需要重置
      if (!val) {
        this.currentMinimized = false
      }
    },
    currentVisible(val) {
      this.dispose()
      if (val) {
        this.zIndex = ++Z_INDEX
        this.$nextTick(this.init)
      }
    },
    maximized: {
      immediate: true,
      handler(val) {
        this.maximizable && (this.currentMaximized = val)
      }
    },
    currentMaximized(val) {
      if (!this.currentVisible) return
      /**
       * 最大化变化时触发
       * @event maximize
       * @param {boolean} 是否最大化
       */
      this.$emit('maximize', val)
      this.$nextTick(() => {
        this.maximize(val)
      })
    },
    minimized: {
      immediate: true,
      handler(val) {
        this.minimizable && (this.currentMinimized = val)
      }
    },
    currentMinimized(val) {
      // 如果组件不可见，最小化失效
      if (!this.currentVisible) return
      this.$nextTick(() => {
        val ? this.hide() : this.show()
        this.setBodyHidden(!val)
      })
    }
  },
  computed: {
    wrapperStyle() {
      return {
        zIndex: this.zIndex,
        left: `${this.viewLeft}px`,
        top: `${this.viewTop}px`
      }
    },
    dialogClass() {
      return {
        'my-dialog': true,
        'my-dialog--src': this.src,
        [this.bodyClass]: !!this.bodyClass
      }
    },
    dialogStyle() {
      const left = Math.max((this.viewWidth - this.originalWidth) / 2, 0),
          top = Math.max((this.viewHeight - this.originalHeight) / 2, 0)
      return {
        left: `${left}px`,
        top: `${top}px`
      }
    },
    iconOptions() {
      if (!this.icon) return null
      return typeof this.icon === 'object'
          ? {
            ...this.icon
          }
          : {
            name: this.icon
          }
    },
    resizeOptions() {
      return {
        minWidth: 300,
        minHeight: 150,
        maxWidth: this.viewWidth,
        maxHeight: this.viewHeight,
        ...(this.resizable || {}),
        disabled: this.currentMaximized ? true : !this.resizable
      }
    },
    dragOptions() {
      return {
        handle: () => {
          if (this.$refs.panel) {
            if (this.$refs.panel.$refs.header) {
              return this.$refs.panel.$refs.header
            }
            return this.$refs.panel
          }
          return null
        },
        range: () => {
          return {
            left: -this.dialogWidth + 10,
            top: 0,
            width: this.viewWidth + this.dialogWidth - 20,
            height: this.viewHeight - 10
          }
        },
        disabled: this.currentMaximized ? true : !this.draggable,
        ...this.draggable
      }
    }
  },
  methods: {
    init() {
      if (!this.$el || this.$el.nodeType !== 1) return
      if (this.target) {
        this.targetDOM = document.querySelector(this.target)
        this.targetDOM && this.targetDOM.appendChild(this.$el)
      }

      if (this.$el.parentNode) {
        addResizeListener(this.$el.parentNode, this.updateView)
      }

      if (!this.draggable && !this.resizable) {
        addResizeListener(this.$el, this.updateView)
      }
      this.updateView()
      /**
       * 窗体打开时触发
       * @event open
       */
      this.$emit('open')
      this.setBodyHidden(true)
    },
    dispose() {
      if (this.$el && this.$el.parentNode) {
        removeResizeListener(this.$el.parentNode, this.updateView)
        if (this.target) {
          this.$el.parentNode.removeChild(this.$el)
        }
      }
      if (!this.draggable && !this.resizable) {
        removeResizeListener(this.$el, this.updateView)
      }

      this.setBodyHidden(false)
      this.dialogWidth = null
      this.dialogHeight = null
      this.currentMinimized = this.minimized
      this.currentMaximized = this.maximized
      this.targetDOM = null
      this.$emit('dispose')

    },
    updateView() {
      if (!this.$el || !this.$refs.dialog) return
      const rect = this.$el.getBoundingClientRect()
      this.viewHeight = rect.height
      this.viewWidth = rect.width
      this.viewLeft = Math.max(document.documentElement.scrollLeft, document.body.scrollLeft)
      this.viewTop = Math.max(document.documentElement.scrollTop, document.body.scrollTop)
      if (this.currentMaximized) {
        this.maximize(true)
      }
      if (!this.dialogWidth || !this.dialogHeight) {
        const dialogRect = this.$refs.dialog.$el.getBoundingClientRect()
        this.originalWidth = this.dialogWidth = getLength(this.viewWidth, this.width) || dialogRect.width
        this.originalHeight = this.dialogHeight = getLength(this.viewHeight, this.height) || dialogRect.height
      }
    },
    // 重置窗体宽高方法：参数为
    redoLayout(opt = {width: null, height: null}) {
      const hHeight = this.$refs.panel.headerHeight
      const fHeight = this.$refs.panel.footerHeight
      const innerNodes = this.$slots.default
      const innerNodeHeight = innerNodes.reduce((total, node) => {
        const nodeHeight = node.elm.offsetHeight || 0
        total += nodeHeight
        return total
      }, 0)
      const newDialogHeight = innerNodeHeight + hHeight + fHeight + 24

      const resizeObj = {
        height: opt.h || newDialogHeight,
        width: opt.w
      }
      this.$nextTick(() => {
        this.originalHeight = resizeObj.height || this.dialogHeight
        this.originalWidth = resizeObj.width || this.dialogWidth
        this.handleResize(resizeObj)
      })
    },
    handleResizeStart(e) {
      /**
       * 开始改变尺寸时触发
       * @event resize-start
       * @param {Object} e
       */
      this.$emit('resize-start', e)
    },
    handleResizeStop(e) {
      /**
       * 停止改变尺寸时触发
       * @event resize-stop
       * @param {Object} e
       */
      this.$emit('resize-stop', e)
    },
    handleResize(e) {
      const {width, height} = e
      this.dialogWidth = width || this.dialogWidth
      this.dialogHeight = height || this.dialogHeight
      /**
       * 改变尺寸时触发
       * @event resize
       * @param {Object} e
       */
      this.$emit('resize', e)
    },
    handleDragStart(e) {
      /**
       * 开始拖拽时触发
       * @event drag-start
       * @param {Object} e
       */
      this.$emit('drag-start', e)
    },
    handleDrag(e) {
      /**
       * 拖拽时触发
       * @event drag
       * @param {Object} e
       */
      this.$emit('drag', e)
    },
    handleDragStop(e) {
      /**
       * 停止拖拽时触发
       * @event drag-stop
       * @param {Object} e
       */
      this.$emit('drag-stop', e)
    },
    handleMousedown() {
      this.zIndex = ++Z_INDEX
    },
    handleClickOutside() {
      if (this.closeOnClickOutside) {
        this.handleClose()
      }
    },
    handleClose() {
      this.currentVisible = false
      this.$emit('update:visible', false)
      /**
       * 窗体关闭时触发
       * @event close
       */
      this.$emit('close')
    },
    handleSubmit() {
      /**
       * 点击确定按钮时触发
       * @event submit
       */
      this.$emit('submit')
    },
    handleCancel() {
      /**
       * 点击取消按钮时触发
       * @event cancel
       */
      this.$emit('cancel')
      if (this.cancelClose) {
        this.handleClose()
      }
    },
    handleSrcLoad() {
      /**
       * iframe完成加载内容时触发
       * @event load
       */
      this.$emit('load')
    },
    maximize(maximized) {
      if (maximized) {
        // 记录全屏之前的窗口尺寸
        if (!this.sizeCaches) {
          this.sizeCaches = {
            dialogWidth: this.dialogWidth,
            dialogHeight: this.dialogHeight
          }
        }
        this.originalWidth = this.dialogWidth = this.viewWidth
        this.originalHeight = this.dialogHeight = this.viewHeight

      } else {
        if (this.sizeCaches) {
          const {dialogWidth, dialogHeight} = this.sizeCaches
          this.originalWidth = this.dialogWidth = dialogWidth
          this.originalHeight = this.dialogHeight = dialogHeight
        }
      }

      if (this.resizable) {
        this.$refs.resize.width = this.originalWidth
        this.$refs.resize.height = this.originalHeight
      }
    },
    show() {
      this.zIndex = ++Z_INDEX
      this.currentMinimized = false
      /**
       * 显示时触发
       * @event show
       */
      this.$emit('show')
    },
    hide() {
      this.currentMinimized = true
      /**
       * 隐藏时触发
       * @event hide
       */
      this.$emit('hide')
    },
    setBodyHidden(hidden) {
      const name = 'my-dialog-hidden-' + this._uid
      const targetDom = this.targetDOM || this.$el.parentNode
      hidden ? addClass(targetDom, name) : removeClass(targetDom, name)
    }

  },
  mounted() {
    this.currentVisible && this.init()
  },
  beforeDestroy() {
    this.dispose()
  }
}
</script>

<style lang="less" >
body[class^=my-dialog-hidden]{
  overflow:hidden
}

.my-dialog{
  box-shadow:0 2px 4px rgba(0,0,0,.12),0 0 6px rgba(0,0,0,.04);
  position:absolute;
  border-radius:4px;
  overflow:hidden;
  pointer-events:auto;
   * {
     box-sizing: border-box;
   }

  &__default{
    overflow:auto!important;
  }

  &__wrapper{
    position:absolute;
    left:0;
    top:0;
    width:100vw;
    height:100vh;
    overflow:hidden;
    pointer-events:none;
  }

  &__modal{
    position:absolute;
    left:0;
    top:0;
    width:100%;
    height:100%;
    background:rgba(0,0,0,.3);
    pointer-events:auto;
  }

  &--src, &-panel__main {
    padding:0;
    overflow:hidden;
  }

  &-panel{
    border-radius:0 0 4px 4px;
    background:#fff;

    &__header {
      height:46px;
      line-height:46px;
      position:relative;
      padding:0 12px;

      .my-icon{
        margin-top:-4px;
        vertical-align:middle!important
      }
    }
    &__title {
      font-size:1.14286rem
    }
    &__icon {
      margin-right:4px
    }
    &__tool {
      position:absolute;
      right:0;
      top:0;
      padding-right:12px;
      font-size:20px;

      .my-icon{
        border-radius:100%;
        background:rgba(0,0,0,.25);
        margin-left:8px;
        cursor:pointer;

        &:hover{
          opacity:.7;
        }
        &.icon-close{
          background: #f5222d;
        }
        &.icon-maximize, &.icon-window {
          background: #52c41a;
        }
        &.icon-minimize{
          background: #faad14;
        }
      }
    }
    &__footer{
      line-height:54px;
      padding:0 12px;
      text-align:right;
    }
    &__main{
      padding:12px;
      width:100%;
      &.is-has-height{
        position:absolute;
        height:100%
      }
    }
    &.is-primary{
      border:1px solid #1890ff;
      border-top:none;

      .my-dialog-panel__header{
        background:#1890ff;
        color:#e6f7ff
      }
      .my-dialog-panel__footer{
        background:rgba(0,0,0,.02)
      }
    }
    &.is-dark{
      border:1px solid #002766;
      border-top:none;

      .my-dialog-panel__header{
        background:#002766;
        color:#e6f7ff;
      }
      .my-dialog-panel__footer{
        background:rgba(0,0,0,.02);
      }
    }
    &.is-light{
      border-top:none;

      .my-dialog-panel__title{
        color:rgba(0,0,0,.85);
      }
      .my-dialog-panel__icon{
        color:#1890ff;
      }
      .my-dialog-panel__tool{
        color:#e6f7ff;
      }
      .my-dialog-panel__header{
        position:relative;

        &:after{
          content:"";
          position:absolute;
          width:100%;
          height:1px;
          overflow:hidden;
          left:0;
          bottom:0;
          background:linear-gradient(90deg,#f100db,#00eced,#0c76e5,green,#ff0,orange,red)
        }
      }
    }
  }

  &-frame{
    width:100%;
    height:100%
  }

}
</style>
